package net.guruqu.rvoView;

import java.awt.Canvas;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Vector;

import net.guruqu.rvoView.MapDefComponent.ActionHint;

import com.gibbon.jme.context.JmeContext;
import com.gibbon.jme.context.RenderPass;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.SharedNode;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;

/**
 * @author Jasper
 * 
 * This class encapsulate the function of giving a top view of the environment
 * map, and allow user to manipulate the camera to navigate throught the map as
 * well as define road map on the view and the agents information( starting
 * point, goal, control point).
 * 
 * class MapDefView gives the manipulation code including manipulating the
 * camera roadmap definition etc.
 * 
 * class MapDefViewImpl exclusively contains the 3d rendering code, including
 * loading 3d model, rendering and rendering system initialization
 */
public class MapDefView {
	public GroundLevelView groundLevelView;

	class MapDefRenderPass extends RenderPass {
		float aspectRatio;
		float centerX, centerY;
		DisplaySystem display;
		float fieldOfView;
		KeyInputAdapter keyAdapter;
		MouseInputAdapter mouseAdapter;
		Node root;
		{
			fieldOfView = 100;
			aspectRatio = 640f / 480f;
			centerX = 0;
			centerY = 0;
			mouseAdapter = new MouseInputAdapter(getMouseAction());
			keyAdapter = new KeyInputAdapter(getKeyAction());
		}

		@Override
		protected void doRender(JmeContext jc) {
			Renderer renderer = jc.getRenderer();
			renderer.clearColorBuffer();
			renderer.clearZBuffer();
			renderer.draw(root);
		}

		@Override
		protected void doUpdate(JmeContext jc) {
			// TODO Auto-generated method stub
			try {
				mouseAdapter.update();
				keyAdapter.update();

				if (cameraNeedUpdate)
					updateCameraLocal();
			} catch (Exception e) {
				e.printStackTrace();
			}
			// super.doUpdate(jc);
		}

		public float getAspectRatio() {
			return aspectRatio;
		}

		public float getCenterX() {
			return centerX;
		}

		public float getCenterY() {
			return centerY;
		}

		public float getFieldOfView() {
			return fieldOfView;
		}

		public InputAction getKeyAction() {
			return new InputAction() {

				@Override
				public void performAction(InputActionEvent evt) {
					JmeContext.get().getCanvas().repaint();
					if (evt.getTriggerCharacter() == '\n') {
						RoadMapUnit rmu = roadMapGroup.getRoadMapUnits()
								.lastElement();
						if (rmu != null && rmu.isEditing() == true
								&& rmu.vertices.size() > 1) {
							rmu.setEditing(false);
						}
					}
				}
			};
		}

		public KeyInputAdapter getKeyAdapter() {
			return keyAdapter;
		}

		public InputAction getMouseAction() {
			return new InputAction() {
				
				public boolean drag0 = false;
				public boolean hitVertex;
				public float lastX = -1, lastY = -1;
				public Object selectedObject;
				public int selectedTarget;
				public int selectedVertex;

				@Override
				public void performAction(InputActionEvent evt) {
					// System.out.println("Drag:"+drag0);
					JmeContext.get().getCanvas().repaint();
					float newX = -5, newY = -5;
					if (evt.getTriggerName().startsWith("X")) {
						newX = evt.getTriggerPosition();
						newY = lastY;
					} else if (evt.getTriggerName().startsWith("Y")) {
						newY = evt.getTriggerPosition();
						newX = lastX;
					} else {
						newX = lastX;
						newY = lastY;
					}
					// System.out.println("X:" + newX + "Y:" + newY);
					if (evt.getTriggerName().startsWith("MOUSE0")) {

						if (evt.getTriggerPressed() && drag0 == false) {
							if (getHintMouseMode() == ActionHint.addRoadLine) {
								RoadMapUnit editUnit = null;
								if (roadMapGroup.getRoadMapUnits().size() == 0
										|| !roadMapGroup.getRoadMapUnits()
												.lastElement().isEditing()) {
									editUnit = roadMapGroup.createNewUnit();
									editUnit.setEditing(true);
								} else {
									editUnit = roadMapGroup.getRoadMapUnits()
											.lastElement();
								}

								editUnit.addVertices(new Vector2f(
										getRealX(newX), getRealY(newY)));

							} else if (getHintMouseMode() == ActionHint.select) {
								{
									selectedObject = roadMapGroup.selectUnit(
											getRealX(newX), getRealY(newY),
											getSpanPixel(1));
									if (selectedObject != null) {
										selectedVertex = ((RoadMapUnit) selectedObject)
												.pick(getRealX(newX),
														getRealY(newY),
														getSpanPixel(1));
									}
								}

								if (selectedObject == null) {
									selectedObject = agentGroup.selectUnit(
											getRealX(newX), getRealY(newY),
											getSpanPixel(1));
									selectedTarget = agentGroup.selectType(
											getRealX(newX), getRealY(newY),
											getSpanPixel(1));
								}
								if (selectedObject == null) {
									selectedObject = controlPointGroup
											.selectUnit(getRealX(newX),
													getRealY(newY),
													getSpanPixel(1));
								}
								if (selectedObject != null)
									hitVertex = true;
							} else if (getHintMouseMode() == ActionHint.addAgent) {
								MapAgent agent = new MapAgent();
								agent.position.set(getRealX(newX),
										getRealY(newY));
								agent.goal.set(getRealX(newX + 0.1f),
										getRealY(newY + 0.1f));
								agentGroup.getAgents().add(agent);
								agentGroup.updateNode();
							} else if (getHintMouseMode() == ActionHint.addControlPoint) {
								controlPointGroup.getControlPoints().add(
										new Vector2f(getRealX(newX),
												getRealY(newY)));
								controlPointGroup.updateNode();
							}
							drag0 = true;
						} else if (evt.getTriggerPressed() == false
								&& drag0 == true) {
							hitVertex = false;
							drag0 = false;
						}

					}

					if (evt.getTriggerName().startsWith("MOUSE1")) {
						{
							selectedObject = roadMapGroup.selectUnit(
									getRealX(newX), getRealY(newY),
									getSpanPixel(1));
							if (selectedObject != null) {
								selectedVertex = ((RoadMapUnit) selectedObject)
										.pick(getRealX(newX), getRealY(newY),
												getSpanPixel(1));
								((RoadMapUnit) selectedObject)
										.removeVertex(selectedVertex);
								if (((RoadMapUnit) selectedObject)
										.getVertices().size() == 0) {
									roadMapGroup
											.removeUnit(((RoadMapUnit) selectedObject));
								}
							}
						}

						{
							selectedObject = agentGroup.selectUnit(
									getRealX(newX), getRealY(newY),
									getSpanPixel(1));
							if (selectedObject != null) {
								agentGroup.getAgents().remove(selectedObject);
								agentGroup.updateNode();
							}
						}
						{
							selectedObject = controlPointGroup.selectUnit(
									getRealX(newX), getRealY(newY),
									getSpanPixel(1));
							if (selectedObject != null) {
								controlPointGroup.getControlPoints().remove(
										selectedObject);
								controlPointGroup.updateNode();
							}
						}
					}

					if (evt.getTriggerName().startsWith("MOUSE2")) {

					}

					if (getHintMouseMode() == ActionHint.select) {
						if (drag0) {
							if (!hitVertex) {
								setCenterX(getCenterX()
										- getSpanX(newX - lastX));
								setCenterY(getCenterY()
										+ getSpanY(newY - lastY));
								// System.out.println(centerX+":"+centerY);
							} else {
								float realX = getRealX(newX);
								float realY = getRealY(newY);
								Vector2f nv3 = new Vector2f(realX, realY);
								if (selectedObject instanceof RoadMapUnit) {
									((RoadMapUnit) selectedObject)
											.updateVertex(selectedVertex, nv3);
								} else if (selectedObject instanceof MapAgent) {
									if (selectedTarget == AgentGroup.ST_AGENT) {
										((MapAgent) selectedObject).position
												.set(nv3);
									} else {
										((MapAgent) selectedObject).goal
												.set(nv3);
									}
									agentGroup.updateNode();
								} else if (selectedObject instanceof Vector2f) {
									((Vector2f) selectedObject).set(nv3);
									controlPointGroup.updateNode();
								}
							}
						}
					}

					lastX = newX;
					lastY = newY;
				}

			};
		}

		public MouseInputAdapter getMouseAdapter() {
			return mouseAdapter;
		}

		public float getRealX(float x) {
			Camera camera = DisplaySystem.getDisplaySystem().getRenderer()
					.getCamera();
			float width = DisplaySystem.getDisplaySystem().getRenderer()
					.getWidth();

			float v1 = camera.getFrustumLeft();
			float v2 = camera.getFrustumRight();
			float xoff = camera.getLocation().x;
			float s = (v2 - v1) * x + v1 + xoff;

			return s;
		}

		public float getRealY(float y) {
			Camera camera = DisplaySystem.getDisplaySystem().getRenderer()
					.getCamera();
			float height = DisplaySystem.getDisplaySystem().getRenderer()
					.getHeight();

			float v1 = camera.getFrustumTop();
			float v2 = camera.getFrustumBottom();
			float offy = camera.getLocation().z;
			float s = (v2 - v1) * y + v1 + offy;
			return s;
		}

		public float getSpanPixel(int span) {
			Camera camera = DisplaySystem.getDisplaySystem().getRenderer()
					.getCamera();
			float width = DisplaySystem.getDisplaySystem().getRenderer()
					.getWidth();

			float v1 = camera.getFrustumLeft();
			float v2 = camera.getFrustumRight();

			float s = (v2 - v1) * span / width;
			return s;
		}

		public float getSpanX(float span) {

			Camera camera = DisplaySystem.getDisplaySystem().getRenderer()
					.getCamera();
			float width = DisplaySystem.getDisplaySystem().getRenderer()
					.getWidth();

			float v1 = camera.getFrustumLeft();
			float v2 = camera.getFrustumRight();

			float s = (v2 - v1) * span;
			return s;
		}

		public float getSpanY(float span) {
			Camera camera = DisplaySystem.getDisplaySystem().getRenderer()
					.getCamera();
			float height = DisplaySystem.getDisplaySystem().getRenderer()
					.getHeight();

			float v1 = camera.getFrustumTop();
			float v2 = camera.getFrustumBottom();

			float s = (v2 - v1) * span;
			return s;
		}

		@Override
		public void initPass(JmeContext context) {
			// TODO Auto-generated method stub
			display = DisplaySystem.getDisplaySystem();
			Camera camera = display.getRenderer().createCamera(640, 480);
			camera.setFrustum(1, 1e4f, -aspectRatio * fieldOfView / 2f,
					aspectRatio * fieldOfView / 2f, -fieldOfView / 2f,
					fieldOfView / 2f);
			camera.setParallelProjection(true);
			// camera.setFrustumPerspective(45, 640.0f / 480.0f, 1, 1000);
			Vector3f loc = new Vector3f(centerX, 900.0f, -centerY);
			camera.setLocation(loc);
			camera.setUp(new Vector3f(0, 0, -1));
			camera.setDirection(new Vector3f(0, -1, 0));
			camera.update();

			display.getRenderer().setCamera(camera);
			display.getRenderer().setBackgroundColor(ColorRGBA.gray);

			root = new Node("root");

			ZBufferState bufferState = display.getRenderer()
					.createZBufferState();
			bufferState.setEnabled(true);
			bufferState.setFunction(ZBufferState.TestFunction.LessThan);
			root.setRenderState(bufferState);
			Node node = null;
			node = (Node) modelRepository.getRepository().get("env");
			SharedNode sn = new SharedNode(node);
			root.attachChild(sn);
			root.attachChild(roadMapGroup.getRenderNode());
			root.attachChild(agentGroup.getRenderNode());
			root.attachChild(controlPointGroup.getRenderNode());
			root.updateModelBound();

			LightState lightState = display.getRenderer().createLightState();
			DirectionalLight directionalLight = new DirectionalLight();
			directionalLight.setDiffuse(new ColorRGBA(0.6f, 0.6f, 0.6f, 1));
			directionalLight.setDirection(new Vector3f(-0.577f, -0.577f,
					-0.577f));
			directionalLight.setEnabled(true);
			lightState.setGlobalAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1));
			lightState.attach(directionalLight);
			lightState.setEnabled(true);

			root.setRenderState(lightState);
			root.updateRenderState();
			canvasInstance.repaint();
		}

		public void resizeCanvas(int w, int h) {
			setAspectRatio((float) w / (float) h);
			canvasInstance.repaint();
		}

		public void setAspectRatio(float aspectRatio) {
			this.aspectRatio = aspectRatio;
			updateCamera();
		}

		public void setCenterX(float centerX) {
			this.centerX = centerX;
			updateCamera();
		}

		public void setCenterY(float centerY) {
			this.centerY = centerY;
			updateCamera();
		}

		public void setFieldOfView(float fieldOfView) {
			this.fieldOfView = fieldOfView;
			updateCamera();
		}

		boolean cameraNeedUpdate;

		public void updateCamera() {
			cameraNeedUpdate = true;
		}

		private void updateCameraLocal() {
			Camera camera = display.getRenderer().getCamera();
			Vector3f loc = new Vector3f(centerX, 900.0f, -centerY);
			camera.setLocation(loc);
			camera.setFrustum(1, 1e4f, -aspectRatio * fieldOfView / 2f / zoom,
					aspectRatio * fieldOfView / 2f / zoom, -fieldOfView / 2f
							/ zoom, fieldOfView / zoom / 2f);
			camera.update();
			cameraNeedUpdate = false;
		}
	}

	static Canvas canvasInstance;

	public static final String MD_ROADMAP_FILE = "MD_ROADMAP_FILE";

	AgentGroup agentGroup;
	ControlPointGroup controlPointGroup;
	ActionHint hintMouseMode;
	ModelRepository modelRepository;;
	HashMap<String, String> properties;
	MapDefRenderPass renderPass;
	RoadMapGroup roadMapGroup;
	float zoom;

	public MapDefView(HashMap<String, String> properties,
			ModelRepository modelRepository) {
		controlPointGroup = new ControlPointGroup();
		roadMapGroup = new RoadMapGroup();
		agentGroup = new AgentGroup();
		this.properties = properties;
		this.modelRepository = modelRepository;
		zoom = 1f;
	}

	public void clear() {
		roadMapGroup.clear();
		controlPointGroup.clear();
		agentGroup.clear();
	}

	public ActionHint getHintMouseMode() {
		return hintMouseMode;
	}

	public ModelRepository getModelRepository() {
		return modelRepository;
	}

	public HashMap<String, String> getProperties() {
		return properties;
	}

	public RoadMapGroup getRoadMapGroup() {
		return roadMapGroup;
	}

	public float getZoom() {
		return zoom;
	}

	public void loadRoadMap() throws Exception {
		clear();
		String saveFile = properties.get(MD_ROADMAP_FILE);
		XMLDecoder decoder = new XMLDecoder(new FileInputStream(saveFile));
		RvoSetupInfo info = (RvoSetupInfo) decoder.readObject();
		{
			LinkedList<LinkedList<Vector2f>> roadMapDef = info.getRoadMaps();
			for (LinkedList<Vector2f> l2f : roadMapDef) {
				RoadMapUnit mapUnit = roadMapGroup.createNewUnit();
				for (Vector2f v2f : l2f) {
					mapUnit.addVertices(v2f);
				}
				mapUnit.setEditing(false);
			}
		}
		{
			LinkedList<MapAgent> agentDef = info.getMapAgents();
			for (MapAgent v2 : agentDef) {
				MapAgent agent = new MapAgent();
				agent.position = v2.getPosition();
				agent.goal = v2.getGoal();
				agentGroup.getAgents().add(agent);
			}
			agentGroup.updateNode();
		}
		{
			LinkedList<Vector2f> cpDef = info.getControlPoints();
			controlPointGroup.getControlPoints().addAll(cpDef);
			controlPointGroup.updateNode();
		}
		groundLevelView.setHorizoneValue(info.getGroundLevel());
		decoder.close();
	}

	public void saveRoadMap() throws Exception {
		String saveFile = properties.get(MD_ROADMAP_FILE);
		XMLEncoder encoder = new XMLEncoder(new FileOutputStream(saveFile));
		// {
		// Vector<LinkedList<Vector2f>> roadMapDef = new
		// Vector<LinkedList<Vector2f>>();
		// for (RoadMapUnit ru : roadMapGroup.getRoadMapUnits()) {
		// roadMapDef.add(ru.getVertices());
		// }
		// encoder.writeObject(roadMapDef);
		// }
		// {
		// Vector<Vector2f[]> agentDef = new Vector<Vector2f[]>();
		// for (MapAgent ma : agentGroup.getAgents()) {
		// agentDef.add(new Vector2f[] { ma.getPosition(), ma.getGoal() });
		// }
		// encoder.writeObject(agentDef);
		// }
		// {
		// encoder.writeObject(controlPointGroup.getControlPoints());
		// }
		RvoSetupInfo info = new RvoSetupInfo();
		info.setAgents(agentGroup);
		info.setControlPoints(controlPointGroup);
		info.setGroundLevel(groundLevelView.getHorizoneValue());
		info.setRoadMap(roadMapGroup);
		encoder.writeObject(info);
		encoder.close();
	}

	public void setCanvas(JmeContext context) {
		if (canvasInstance != null)
			throw new RuntimeException("Canvas Instance already set!");
		Canvas canvas = context.getCanvas();
		renderPass = new MapDefRenderPass();
		context.getPassManager().add(renderPass);
		canvasInstance = canvas;
		canvasInstance.addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent ce) {
				renderPass.resizeCanvas(canvasInstance.getWidth(),
						canvasInstance.getHeight());
				
			}
		});
		renderPass.getMouseAdapter().setup(canvasInstance);
		renderPass.getKeyAdapter().setup(canvasInstance);
		canvas.repaint();
//		canvasInstance.addKeyListener(renderPass.getKeyAdapter());
	}

	public void setHintMouseMode(ActionHint hintMouseMode) {
		this.hintMouseMode = hintMouseMode;
	}

	public void setModelRepository(ModelRepository modelRepository) {
		this.modelRepository = modelRepository;
	}

	public void setProperties(HashMap<String, String> properties) {
		this.properties = properties;
	}

	public void setZoom(float zoom) {
		this.zoom = zoom;
		renderPass.updateCamera();
	}

}
